/***************************************************************************
 *
 *   Copyright (C) 2011 by Paul Demorest
 *   Licensed under the Academic Free License version 2.1
 *
 ***************************************************************************/
#include "dsp/GUPPIBlockFile.h"
#include "dsp/BitSeries.h"

#include "Error.h"

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#include "fitshead.h"
#include "fitshead_utils.h"

using namespace std;

dsp::GUPPIBlockFile::GUPPIBlockFile (const char* name)
  : File (name)
{
  hdr = NULL;
  dat = NULL;
  time_ordered = true;
  signed_8bit = true;
  current_block_byte = 0;
  current_nzero_byte = 0;
  overlap = 0;
  blocsize = 0;
  current_pktidx = -1;
  set_overlap_buffer(new BitSeries);
}

dsp::GUPPIBlockFile::~GUPPIBlockFile ( )
{
}

// Use for required header params
#define header_get_check(param, var) {\
  rv = hget(hdr, param, var); \
  if (rv==0) \
    throw Error (InvalidState, "dsp::GUPPIBlockFile::parse_header", \
        "Couldn't find %s keyword in header.", param); \
}

void dsp::GUPPIBlockFile::parse_header()
{

  // Read header params
  int rv, itmp;
  long long ltmp;
  double ftmp;
  char ctmp[80], ctmp2[80];

  header_get_check("NBITS", &itmp);
  get_info()->set_nbit(itmp);

  header_get_check("OBSBW", &ftmp);
  get_info()->set_bandwidth(ftmp);

  header_get_check("OBSFREQ", &ftmp);
  get_info()->set_centre_frequency(ftmp);
 
  header_get_check("OBSNCHAN", &itmp);
  get_info()->set_nchan(itmp);

  header_get_check("NPOL", &itmp);
  if (itmp==1)
    get_info()->set_npol(1);
  else 
    get_info()->set_npol(2);

  // Default to complex data
  get_info()->set_state(Signal::Analytic);

  if (get_info()->get_nchan() == 1) 
    get_info()->set_state(Signal::Nyquist);

  // Any format-specific checks
  header_get_check("PKTFMT", ctmp);
  header_get_check("PKTSIZE",  &packet_size);
  if (string(ctmp) == "VDIF") 
  {
    get_info()->set_state(Signal::Nyquist);
    signed_8bit = false;
    // Assume 2-pol VDIF used two streams, this will correct
    // packets per block later.
    if (get_info()->get_npol()==2) { packet_size *= 2; }
  } 
  else if (string(ctmp) == "1SFA") 
  {
    if (get_info()->get_nbit()==2) { packet_size /= 4; }
  }
  else if (string(ctmp) == "SIMPLE")
    time_ordered = false;
 
  header_get_check("TBIN", &ftmp);
  get_info()->set_rate(1.0/ftmp);

  // Data block size params
  header_get_check("OVERLAP", &itmp);
  overlap = itmp;
  header_get_check("BLOCSIZE", &itmp);
  blocsize = itmp;

  int imjd, smjd;
  double t_offset;
  header_get_check("STT_IMJD", &imjd);
  header_get_check("STT_SMJD", &smjd);
  header_get_check("STT_OFFS", &t_offset);
  header_get_check("PKTIDX",   &ltmp);
  current_pktidx = ltmp;
  t_offset += ltmp * packet_size * 8.0 / get_info()->get_rate() / 
      (get_info()->get_nbit() * get_info()->get_nchan() * get_info()->get_npol() * get_info()->get_ndim());
  //cerr << "t_offset=" << t_offset << "s" << endl;
  MJD epoch (imjd, (double)smjd/86400.0 + t_offset/86400.0);
  get_info()->set_start_time(epoch);

  header_get_check("TELESCOP", ctmp);
  get_info()->set_telescope(ctmp);

  header_get_check("SRC_NAME", ctmp);
  get_info()->set_source(ctmp);

  header_get_check("BACKEND", ctmp);
  get_info()->set_machine(ctmp);

  // Maybe the following aren't strictly required ...

  // Poln type
  header_get_check("FD_POLN", ctmp);
  if (strncasecmp(ctmp, "CIR", 3)==0) 
    get_info()->set_basis(Signal::Circular);
  else
    get_info()->set_basis(Signal::Linear);

  // Coordinates
  sky_coord coords;
  header_get_check("RA_STR", ctmp);
  header_get_check("DEC_STR", ctmp2);
  if (verbose)
    cerr << "dsp::GUPPIBlockFile::parse_header ra_str=" 
      << ctmp << " dec_str=" << ctmp2 << endl;
  coords.setHMSDMS(ctmp, ctmp2);
  get_info()->set_coordinates(coords);

  // Receiver
  header_get_check("FRONTEND", ctmp);
  get_info()->set_receiver(ctmp);
  // How to set feed hand, symm angle, etc?
  // Note: GBT recvrs have fd_hand=-1, PF has fd_sang=+45deg, 
  //       otherwise fd_sang=-45deg.
  
}

//! Send data bytes to unpacker
// This "untransposes" the GUPPI block structure if necessary.
// NOTE: Could handle this by setting resolution to only
// allow reads in multiples of the block size.  But this 
// would constrain the allowed block sizes for processing.
int64_t dsp::GUPPIBlockFile::load_bytes (unsigned char *buffer, uint64_t nbytes)
{
  if (verbose) 
    cerr << "dsp::GUPPIBlockFile::load_bytes() nbytes=" << nbytes << endl;

  const unsigned nchan = get_info()->get_nchan();
  const unsigned npol = get_info()->get_npol();
  const unsigned nbit = get_info()->get_nbit();
  const unsigned bytes_per_samp = (2 * npol * nbit) / 8;
  const uint64_t overlap_bytes = overlap * bytes_per_samp * nchan;
  const uint64_t blocsize_per_chan = blocsize / nchan;
  uint64_t to_load = nbytes;
  uint64_t bytes_read = 0;

  uint64_t packets_per_block = (blocsize - overlap_bytes) / packet_size;

  while (to_load && !end_of_data) 
  {

    // If there are zeros, send them
    if (current_nzero_byte)
    {
      uint64_t to_zero = current_nzero_byte;;
      if (to_zero > to_load)
        to_zero = to_load;

      memset(buffer + bytes_read, 0, to_zero);
      bytes_read += to_zero;
      to_load -= to_zero;
      current_nzero_byte -= to_zero;

      continue;
    }

    // Only read non-overlapping part of data
    uint64_t to_read = (blocsize - overlap_bytes) - current_block_byte;
    if (to_read > to_load) 
      to_read = to_load;

    // Easy case where channels are interleaved or only have 1 channel
    if (time_ordered == false || nchan == 1)
    {
      memcpy(buffer + bytes_read, dat + current_block_byte, to_read);
      bytes_read += to_read;
      current_block_byte += to_read;
    }

    // More complicated case where channel blocks are time-ordered
    else
    {

      uint64_t to_read_per_chan = to_read / nchan;
      uint64_t nsamp_per_chan = to_read_per_chan / bytes_per_samp;
      uint64_t cur_byte = current_block_byte / nchan;

      for (unsigned isamp=0; isamp<nsamp_per_chan; isamp++) {
        for (unsigned ichan=0; ichan<nchan; ichan++) 
        {
          memcpy(buffer + bytes_read, 
              dat + cur_byte + ichan*blocsize_per_chan + isamp*bytes_per_samp,
              bytes_per_samp);
          bytes_read += bytes_per_samp;
        }
      }

      current_block_byte += to_read;

    }

    to_load -= to_read;

    // Get next block if necessary
    if (current_block_byte == blocsize - overlap_bytes)
    {

      // Read blocks, skipping any that are out of order
      long long new_pktidx = 0;
      long long pkt_diff = 0;
      while (pkt_diff <= 0)
      {
        // Print warning on out of order block
        if (pkt_diff < 0)
          cerr << "dsp::GUPPIBlockFile::load_bytes() unordered pktidx=" 
            << new_pktidx << " last=" << current_pktidx
            << " diff=" << pkt_diff
            << " inc=" << packets_per_block
            << endl;
        
        // load_next_block will return 0 if no more data.
        int rv = load_next_block();
        if (rv==0) 
        {
          end_of_data = true;
          break;
        }
        current_block_byte = 0;

        header_get_check("PKTIDX", &new_pktidx);
        pkt_diff = new_pktidx - current_pktidx;
      }

      // Check for skipped blocks
      if ((pkt_diff != packets_per_block) && !end_of_data)
      {
        // Print a warning
        cerr << "dsp::GUPPIBlockFile::load_bytes() skipped pktidx=" 
          << new_pktidx << " last=" << current_pktidx
          << " diff=" << pkt_diff
          << " inc=" << packets_per_block
          << endl;

        // Only can handle an integer number of missed blocks
        if (pkt_diff % packets_per_block) 
        {
          cerr << "dsp::GUPPIBlockFile::load_bytes() "
            << "WARNING fraction skipped block" 
            << endl;
        }
	else
	{
	  int nblock_miss = (pkt_diff / packets_per_block) - 1;
	  current_nzero_byte = (blocsize - overlap_bytes) * nblock_miss;
	}

      }

      current_pktidx = new_pktidx;
    }

  }

  return nbytes - to_load;

}
